/*
 * CITS1002 Project 2 2012
 * Name:             Guilherme R. Lampert
 * Student number:   21203005
 * Date:             02/10/20012
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

/*
 * Creates a directory tree or path.
 * Return 0 on success, errno otherwise.
 */
static int RecursiveMkdir(const char * pathEndedWithSlashOrFilename) {

	int result = 0;
	char * dirPath = strdup(pathEndedWithSlashOrFilename);

	if (dirPath == NULL) {
		/* Impossible to allocate memory for the filename */
		result = ENOMEM;
	} else {
		char * begin, * end;
		struct stat subDirStat;

		begin = end = dirPath;

		while ((result == 0) && (*end != '\0')) {
			if ((*end == '/') || (*end == '\\')) {
				*end = '\0';
				if (stat(dirPath, &subDirStat) != 0) {
					if (mkdir(dirPath, 0777) != 0) { /* 0777 is with ALL permissions */
						/* Impossible to create subdirectory */
						result = errno;
					}
				} else {
					if (!(subDirStat.st_mode & S_IFDIR)) {
						/* There is a file with the same name as the directory! */
						result = -1;
					} else {
						if (!(subDirStat.st_mode & S_IWRITE)) {
							/* Cannot write to this dir! */
							result = -1;
						} else {
							/* OK, directory exists already! */
						}
					}
				}
				*end = '/';
			}
			end++;
		}
		free(dirPath);
	}

	return (result);
}

/*
 * Copy a small block of a file, of size BUFSIZ or less from the destination
 * to the source file. Returns the number of bytes copied.
 * FIXME: Errors are ignored!
 */
static off_t CopySmallBlock(int fromFd, int toFd, off_t bytesToCopy) {

	size_t totalBytes = (size_t)(bytesToCopy > BUFSIZ) ? BUFSIZ : bytesToCopy;
	char smallBuf[BUFSIZ];

	size_t bytesRead = read(fromFd, smallBuf, totalBytes);
	assert(bytesRead == totalBytes);

	size_t bytesWritten = write(toFd, smallBuf, bytesRead);
	assert(bytesWritten == bytesRead);

	return (totalBytes);
}

/* ----------------------------------------------------------------------------- */

/*
 * Get the length in bytes of a file.
 */
int LengthOfFile(const char * filename, off_t * fileLen) {

	struct stat statBuf;
	if (stat(filename, &statBuf) == 0) {
		*fileLen = statBuf.st_size;
		return (0);
	} else {
		return (errno);
	}
}

/*
 * Copies a source file to a destination file, overwriting
 * any existing file with the same name as the destination.
 * If the destination is nested inside a directory tree, that tree is
 * created if not already existent.
 * Returns 0 on success and does the copy, errno on failure and
 * nothing copied. (Directory paths may be partially created tough)
 */
int CopyFile(const char * from, const char * to) {

	assert(from != NULL && to != NULL);

	/* Try to open source file: */
#if defined(O_BINARY)
    int fromFd = open(from, (O_RDONLY | O_BINARY), 0);
#else
    int fromFd = open(from, O_RDONLY, 0);
#endif

	/* Failed to open source file :( */
	if (fromFd < 0) {
		return (errno);
	}

	int result = RecursiveMkdir(to);

	if (result == 0) {
	/* Try to open destination file: */
#if defined(O_BINARY)
	    int toFd = open(to, (O_CREAT | O_WRONLY | O_TRUNC | O_BINARY), S_IRWXU);
#else   /* NOTE: Creating file with read/write/execute permissions! */
		int toFd = open(to, (O_CREAT | O_WRONLY | O_TRUNC), S_IRWXU);
#endif
		if (toFd >= 0) {
			/* Ready to write to dest file */
			off_t fileLen, copied, left;
			result = LengthOfFile(from, &fileLen);
			if (result == 0) {
				copied = 0;
				left = fileLen;
				while (left > 0) {
					/* Copy small pieces of the file, one at a time
					 * just to avoid a memory allocation which could
					 * potentially fail if the file is too big */
					copied = CopySmallBlock(fromFd, toFd, left);
					left -= copied;
				}
			}
			close(toFd);
		} else {
			result = errno;
		}
	}

	close(fromFd);

	return (result);
}
